﻿using UnityEngine;
using System.Collections;
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

namespace CHFramework
{
    public enum EmptyLineBehaviour
    {
        /// <summary>
        /// Empty lines are interpreted as a line with zero columns.
        /// 可以理解为，没有一列的一行
        /// </summary>
        NoColumns,
        /// <summary>
        /// Empty lines are interpreted as a line with a single empty column.
        /// 可以理解为，只有一空列的一行
        /// </summary>
        EmptyColumn,
        /// <summary>
        /// Empty lines are skipped over as though they did not exist.
        /// 可以理解为，一行数据为空时跳过不读取
        /// </summary>
        Ignore,
        /// <summary>
        /// An empty line is interpreted as the end of the input file.
        /// 可以理解为，数据表的最后
        /// </summary>
        EndOfFile,
    }

    /// <summary>
    /// CSV 文件的读写类的基类
    /// </summary>
    public abstract class CsvFileCommon
    {
        /// <summary>
        /// 在CSV文件中的特殊字符。如果一列中有这些字符中的任何一个，那么整个这一列被两个该字符包括,如""
        /// </summary>
        protected char[] SpecialChars = new char[] { ',', '"', '\r', '\n' };

        /// <summary>
        /// 这些有特殊含义的特殊字符的索引
        /// </summary>
        private const int DelimiterIndex = 0;
        private const int QuoteIndex = 1;

        /// <summary>
        /// 索引为DelimiterIndex的字符
        /// </summary>
        public char Delimiter
        {
            get { return SpecialChars[DelimiterIndex]; }
            set { SpecialChars[DelimiterIndex] = value; }
        }

        /// <summary>
        /// 索引为QuoteIndex的字符
        /// </summary>
        public char Quote
        {
            get { return SpecialChars[QuoteIndex]; }
            set { SpecialChars[QuoteIndex] = value; }
        }
    }

    /// <summary>
    /// Class for reading from comma-separated-value (CSV) files
    /// 读取Csv文件的类
    /// </summary>
    public class CsvFileReader : CsvFileCommon, IDisposable
    {
        private StreamReader Reader;
        private string CurLine;
        private int CurPos;
        private EmptyLineBehaviour EmptyLineBehaviour;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="emptyLineBehaviour"></param>
        public CsvFileReader(Stream stream, EmptyLineBehaviour emptyLineBehaviour = EmptyLineBehaviour.NoColumns)
        {
            Reader = new StreamReader(stream);
            EmptyLineBehaviour = emptyLineBehaviour;
        }

        public CsvFileReader(string path, EmptyLineBehaviour emptyLineBehaviour = EmptyLineBehaviour.NoColumns)
        {
            Reader = new StreamReader(path);
            EmptyLineBehaviour = emptyLineBehaviour;
        }

        public bool ReadRow(List<string> columns)
        {
        ReadNextLine:
            CurLine = Reader.ReadLine();
            CurPos = 0;
            if (CurLine == null)
                return false;
            if (CurLine.Length == 0)
            {
                switch (EmptyLineBehaviour)
                {
                    case EmptyLineBehaviour.NoColumns:
                        columns.Clear();
                        return true;
                    case EmptyLineBehaviour.Ignore:
                        goto ReadNextLine;
                    case EmptyLineBehaviour.EndOfFile:
                        return false;
                }
            }

            string column;
            int numColumns = 0;
            while (true)
            {
                if(CurPos < CurLine.Length && CurLine[CurPos] == Quote)
                    column = ReadQuotedColumn();
                else
                    column = ReadUnquotedColumn();
                // Add column to list
                if (numColumns < columns.Count)
                    columns[numColumns] = column;
                else
                    columns.Add(column);
                numColumns++;
                // Break if we reached the end of the line
                if (CurLine == null || CurPos == CurLine.Length)
                    break;
                // Otherwise skip delimiter
                Debug.Assert(CurLine[CurPos] == Delimiter);
                CurPos++;
            }
            // Remove any unused columns from collection
            if (numColumns < columns.Count)
                columns.RemoveRange(numColumns, columns.Count - numColumns);
            // Indicate success
            return true;
        }

        /// <summary>
        /// Reads a quoted column by reading from the current line until a
        /// closing quote is found or the end of the file is reached. On return,
        /// the current position points to the delimiter or the end of the last
        /// line in the file. Note: CurLine may be set to null on return.
        /// </summary>
        private string ReadQuotedColumn()
        {
            // Skip opening quote character
            Debug.Assert(CurPos < CurLine.Length && CurLine[CurPos] == Quote);
            CurPos++;

            // Parse column
            StringBuilder builder = new StringBuilder();
            while (true)
            {
                while (CurPos == CurLine.Length)
                {
                    // End of line so attempt to read the next line
                    CurLine = Reader.ReadLine();
                    CurPos = 0;
                    // Done if we reached the end of the file
                    if (CurLine == null)
                        return builder.ToString();
                    // Otherwise, treat as a multi-line field
                    builder.Append(Environment.NewLine);
                }

                // Test for quote character
                if (CurLine[CurPos] == Quote)
                {
                    // If two quotes, skip first and treat second as literal
                    int nextPos = (CurPos + 1);
                    if (nextPos < CurLine.Length && CurLine[nextPos] == Quote)
                        CurPos++;
                    else
                        break;  // Single quote ends quoted sequence
                }
                // Add current character to the column
                builder.Append(CurLine[CurPos++]);
            }

            if (CurPos < CurLine.Length)
            {
                // Consume closing quote
                Debug.Assert(CurLine[CurPos] == Quote);
                CurPos++;
                // Append any additional characters appearing before next delimiter
                builder.Append(ReadUnquotedColumn());
            }
            // Return column value
            return builder.ToString();
        }

        /// <summary>
        /// Reads an unquoted column by reading from the current line until a
        /// delimiter is found or the end of the line is reached. On return, the
        /// current position points to the delimiter or the end of the current
        /// line.
        /// </summary>
        private string ReadUnquotedColumn()
        {
            int startPos = CurPos;
            CurPos = CurLine.IndexOf(Delimiter, CurPos);
            if (CurPos == -1)
                CurPos = CurLine.Length;
            if (CurPos > startPos)
                return CurLine.Substring(startPos, CurPos - startPos);
            return String.Empty;
        }

        // Propagate Dispose to StreamReader
        public void Dispose()
        {
            Reader.Dispose();
        }
    }

    /// <summary>
    /// Class for writing to comma-separated-value (CSV) files.
    /// </summary>
    public class CsvFileWriter : CsvFileCommon, IDisposable
    {
        // Private members
        private StreamWriter Writer;
        private string OneQuote = null;
        private string TwoQuotes = null;
        private string QuotedFormat = null;

        /// <summary>
        /// Initializes a new instance of the CsvFileWriter class for the
        /// specified stream.
        /// </summary>
        /// <param name="stream">The stream to write to</param>
        public CsvFileWriter(Stream stream)
        {
            Writer = new StreamWriter(stream);
        }

        /// <summary>
        /// Initializes a new instance of the CsvFileWriter class for the
        /// specified file path.
        /// </summary>
        /// <param name="path">The name of the CSV file to write to</param>
        public CsvFileWriter(string path)
        {
            Writer = new StreamWriter(path);
        }

        /// <summary>
        /// Writes a row of columns to the current CSV file.
        /// </summary>
        /// <param name="columns">The list of columns to write</param>
        public void WriteRow(List<string> columns)
        {
            // Verify required argument
            if (columns == null)
                throw new ArgumentNullException("columns");

            // Ensure we're using current quote character
            if (OneQuote == null || OneQuote[0] != Quote)
            {
                OneQuote = String.Format("{0}", Quote);
                TwoQuotes = String.Format("{0}{0}", Quote);
                QuotedFormat = String.Format("{0}{{0}}{0}", Quote);
            }

            // Write each column
            for (int i = 0; i < columns.Count; i++)
            {
                // Add delimiter if this isn't the first column
                if (i > 0)
                    Writer.Write(Delimiter);
                // Write this column
                if (columns[i].IndexOfAny(SpecialChars) == -1)
                    Writer.Write(columns[i]);
                else
                    Writer.Write(QuotedFormat, columns[i].Replace(OneQuote, TwoQuotes));
            }
            Writer.WriteLine();
        }

        // Propagate Dispose to StreamWriter
        public void Dispose()
        {
            Writer.Dispose();
        }
    }
}
